

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.zip.CRC32;

/**
 * 
 * @author 
 * 
 * | Preamble | Start  | Dest Addr | Source Addr | Type    |     Payload     |   CRC   |
 * <BR></BR>
 * | 7 bytes  | 1 byte |  6 bytes  |   6 bytes   | 2 bytes | 46 - 1500 bytes | 4 bytes |
 * 
 */
public class EthernetFrame
{
	private byte[] frame;
	private byte[] des;
	private byte[] src;
	private byte[] type;
	private byte[] payload;
	private byte[] crc;
			
    private final int BYTE_OFFSET = 128;
    private final int MAX_SINGLE_DIGIT = -112;
    private final int FF_AS_BYTE = 127;
	
	
	//constructor for disassembling Ethernet frames given a byte array
    public EthernetFrame(byte[] data) throws InstantiationException
    {
        // Minimum length of a valid frame is 72 bytes. (subtracted the length of preamble to get 64)
        // Maximum length of a valid frame is 1526 bytes.
        if (data.length < 72 || data.length > 1526)     	
        	throw new InstantiationException("Frame not built: Invalid frame length.");

        frame = data;
        des = getDestinationAddress();
        src = getSourceAddress();
        type = getFrameType();
        payload = getFramePayload();
        crc = getFrameCRC();
   
 
        if (!isValidFrame())
        {
        	System.out.print("\nCheck both CRC's: " + hexify(crc) + " vs ");
        	System.out.println(hexify(generateCRC()));
        	throw new InstantiationException("Frame not built: Invalid CRC."); 
        }
    }

    
    //constructor for assembling Ethernet frames given components
    public EthernetFrame(MacAddress des, MacAddress src, byte[] type, byte[] payload) 
    {
    	this.des = des.toByteArray();
    	this.src = src.toByteArray();
    	this.type = type;
    	this.payload = payload;
    	crc = generateCRC();
    }
    
    public byte[] toByte()
    {
    	//if the preamble is needed
    	
    	//26 for the sum of the sizes of preamble[8], source[6], destination[6], type[2], and crc[4]
    	byte [] ethernetOut = new byte [26 + payload.length];
    	byte [] preamble = {42,42,42,42,42,42,42,43};
    	
    	//System.arracopy is used to simply copy over multiple byte arrays to on single byte array
    	System.arraycopy(preamble, 0, ethernetOut, 0, 8);
    	System.arraycopy(des, 0, ethernetOut, 8, 6);
    	System.arraycopy(src, 0, ethernetOut, 14, 6);
    	System.arraycopy(type, 0, ethernetOut, 20, 2);
    	System.arraycopy(payload, 0, ethernetOut, 22, payload.length);
    	System.arraycopy(crc, 0, ethernetOut, 22 + payload.length, 4);
    	
    	//if the preamble is not needed
//    	//18 for the sum of the sizes of source[6], destination[6], type[2], and crc[4]
//    	byte [] ethernetOut = new byte [18 + payload.length];
//    	
//    	
//    	//System.arracopy is used to simply copy over multiple byte arrays to on single byte array
//    	
//    	System.arraycopy(des, 0, ethernetOut, 0, 6);
//    	System.arraycopy(src, 0, ethernetOut, 6, 6);
//    	System.arraycopy(type, 0, ethernetOut, 12, 2);
//    	System.arraycopy(payload, 0, ethernetOut, 14, payload.length);
//    	System.arraycopy(crc, 0, ethernetOut, 14 + payload.length, 4);
    	
    	
    	return ethernetOut;
    }
    
    
    private byte[] getDestinationAddress() {
        // Bytes 8 - 14 of the frame contain the destination address.
        byte[] destinationAddress = new byte[6];
        
        // Read bytes.
        for (int i = 8; i < 14; i++)
        	destinationAddress[i - 8] = frame[i];
         
        return destinationAddress;
    }

    private byte[] getSourceAddress() {
        // Bytes 16 - 22 of the frame contain the source address.
        byte[] sourceAddress = new byte[6];

        // Read bytes.
        for (int i = 14; i < 20; i++)
        	sourceAddress[i - 14] = frame[i];
        
        return sourceAddress;
    }

    private byte[] getFrameType() {
        // Bytes 23 - 24 of the frame contain the frame type.
        byte[] frameType = new byte[2];

        // Read bytes.
        for (int i = 20; i < 22; i++)
        	frameType[i - 20] = frame[i];
 
       
        return frameType;
    }

    public byte[] getFramePayload() {
        // Bytes 25 - 1525 of the frame contain the frame's payload.
        byte[] framePayload = new byte[frame.length - 4 - 22];

        // Read bytes.
        for (int i = 22; i < frame.length - 4; i++)
        	framePayload[i - 22] = frame[i];

        return framePayload;
    }

    public byte[] getFrameCRC() {
        // The last 4 bytes of the frame contain the CRC data.
        byte[] frameCRC = new byte[4];
        
        // Read bytes.
        for (int i = frame.length - 4 ; i < frame.length; i++)	
        	frameCRC[i - (frame.length - 4)] = frame[i];

        return frameCRC;
    }

    /**
     * Assembles an ethernet frame.
     */
    

    /**
     * Disassembles an ethernet frame.
     */
    // TODO: take in byte arrray and convert to bid endian and most significant bit order
    private void disassembleFrame() 
    {

    }

    /**
     * Validates the integrity of the ethernet frame using the CRC data.
     */
    private boolean isValidFrame() 
    {	
    	//temporary byte array contains a CRC value from generateCRC() to test against the value of the incoming value
    	byte [] temp = new byte [4];
    	temp = generateCRC();
    	
    	if(Arrays.equals(temp, getFrameCRC()))
    		return true;
    	else
    		return false;
    }
    
    
    /**
     * Creates a new CRC to be put into the Ethernet Frame.
     */
    public byte[] generateCRC()
    {
    	//14 for the sum of the sizes of source, destination, and type
    	byte [] partialFrame = new byte [14 + this.payload.length];
    	byte [] temp;
    	byte [] generated = new byte [4];
    	
    	/*
    	 * Create an array of the part of the Ethernet Frame used for creating the CRC
    	 * Components used are destination, source, type, and payload
    	 */
    	
    	System.arraycopy(des, 0, partialFrame, 0, 6);
    	System.arraycopy(src, 0, partialFrame, 6, 6);
    	System.arraycopy(type, 0, partialFrame, 12, 2);
    	System.arraycopy(payload, 0, partialFrame, 14, payload.length); 	
    	
    	//With the newly created CRC, the long is converted to a byte array
    	CRC32 newChecksum = new CRC32();
    	newChecksum.update(partialFrame);
    	ByteBuffer bb = ByteBuffer.allocate(8);  
    	temp = bb.putLong(newChecksum.getValue()).array();  
    	
    	//Since Longs are 8 bytes, the first four bytes, which seem to be padded with zeros, are removed
    	System.arraycopy(temp, 4, generated, 0, 4);
    	
    	return generated;
    }
    

    /**
     * Converts byte array to hex
     */
    public String hexify(byte [] n)
    {
    	StringBuilder temp = new StringBuilder();

    	for (int i = 0; i < n.length; i++) 
    	{
    	    // Pad a leading '0' to single-digit values of the pair.
    	    if (n[i] < MAX_SINGLE_DIGIT) 
    	    	temp.append("0");
	    

    	    /* Adjust the value of the pair by 128 so the range is 0 to 255.
    	     * and represent the pair's value as a hex string.
    	     */
    	    temp.append(Integer.toHexString((n[i] + BYTE_OFFSET)));
    	}	
    	
    	String result = temp.toString();
    	
    	return result;
    }
    
    
    /**
     * toString overridden to return addresses and the size of the payload
     */
    @Override
	public String toString()
    {
    	MacAddress destination = new MacAddress(des);
    	MacAddress source = new MacAddress(src);
   
    	
    	return "Des MAC " + destination.toString() + "\nSrc MAC " + source.toString() + 
    			 "\nPaload length " + payload.length + "\nCRC: " + hexify(crc);
    }
    
    
    
    public static void main(String[]args)
    {
    	ByteBuffer z = ByteBuffer.allocate(72);
		byte [] test = new byte [72];

		// Populate the test ethernet frame.
		// Preamble.
		for (int i = 0; i < 7; i++)
		{
			test[i] = 42;
		}

		// Start of frame.
		test[7] = 43;

		// Destination address.
		for (int i = 8; i < 14; i++)
		{
			test[i] = 15;
		}

		// Source address.
		for (int i = 14; i < 20; i++)
		{
			test[i] = 20;
		}

		// Type / length.
		test[20] = -128;
		test[21] = -128;

		// Payload / data.
		for (int i = 22; i < 68; i++)
		{
			test[i] = -101;
		}

		// CRC.
		test[68] = 103;
		test[69] = -128;
		test[70] = -8;
		test[71] = -20;


		z.put(test);

		EthernetFrame eframe;
			try {
				eframe = new EthernetFrame(z.array());
				
			 	System.out.println(eframe.toString());
		    	
			 	
		    	byte [] partialFrame = new byte [4];
		    	partialFrame = eframe.generateCRC();
		    	
		    	EthernetFrame eframe3 = new EthernetFrame(eframe.toByte());
		    
			} catch (InstantiationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

    }

    
    
    
}
